﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace StockLearner
{
    class NN
    {
        double[,] w1; // weights from input to hidden layer
        double[,] w2; // weigths from hidden to output layer
        double rate; // learning rate
        int icount, hcount, ocount; // number of input, hidden and output nodes

        double[] ins;
        double[] hid;
        double[] outs;
        double[] hidsums;
        double[] outsums;

        public NN(int inputs, int hidden, int outputs, double rate)
        {
            w1 = new double[inputs + 1, hidden];
            w2 = new double[hidden + 1, outputs];
            this.rate = rate;
            icount = inputs;
            hcount = hidden;
            ocount = outputs;

            Random rand = new Random();

            for (int h = 0; h < hcount; h++)
            {
                for (int i = 0; i <= icount; i++)
                {
                    w1[i, h] = (rand.NextDouble() - 0.5) / 10;
                }
            }

            for (int o = 0; o < ocount; o++)
            {
                for (int h = 0; h <= hcount; h++)
                {
                    w2[h, o] = (rand.NextDouble() - 0.5) / 10;
                } 
            }
            

        }

        public double[] input(double[] inputs)
        {
            outs = new double[ocount];
            hid = new double[hcount];
            hidsums = new double[hcount];
            outsums = new double[ocount];
            
            // probagate to the hidden layer
            for(int h = 0; h < hcount; h++)
            {
                hidsums[h] = w1[0,h]; // start with bias
                for (int i = 0; i < icount; i++) // add all inputs
                {
                    hidsums[h] += inputs[i] * w1[i+1, h];
                }
                hid[h] = sig(hidsums[h]);
            }

            // hidden layer to output layer
            for (int o = 0; o < ocount; o++)
            {
                outsums[o] = w2[0, o]; // start with bias
                for (int h = 0; h < hcount; h++) // add all inputs
                {
                    outsums[o] += hid[h] * w2[h + 1, o];
                }
                outs[o] = sig(outsums[o])+0.5;
            }

            return outs;
        }

        private void backprop(double[] target)
        {
            double[] deltak = new double[ocount];

            for (int o = 0; o < ocount; o++)
                deltak[o] = (target[o] - outs[o]) * dsig(outsums[o]);

            double[] deltaj = new double[hcount+1];
            for (int h = 0; h <= hcount; h++)
            {
                deltaj[h] = 0;
                for (int o = 0; o < ocount; o++)
                {
                    deltaj[h] += w2[h, o] * deltak[o];
                }
                if (h == 0)
                    deltaj[h] *= dsig(1);
                else
                    deltaj[h] *= dsig(hidsums[h - 1]);
            }

            for (int i = 0; i <= icount; i++)
                for (int h = 0; h < hcount; h++)
                    w1[i, h] += rate * deltaj[h + 1] * ((i == 0) ? 1 : ins[i - 1]);

            for (int h = 0; h <= hcount; h++)
                for (int o = 0; o < ocount; o++)
                    w2[h, o] += rate * deltak[o] * ((h == 0) ? 1 : hidsums[h - 1]);

        }

        private void backprop2(double[] target)
        {
            double[] oe = new double[ocount]; // error in outputs
            double[] he = new double[hcount + 1];
            double[] ie = new double[icount + 1];

            // find errors in the output nodes
            for (int i = 0; i < ocount; i++)
            {
                oe[i] = target[i] - outs[i];
            }

            // find errors in hidden layer
            for (int h = 0; h <= hcount; h++)
            {
                he[h] = 0;
                for (int o = 0; o < ocount; o++)
                {
                    he[h] += oe[o] * w2[h, o];
                }
            }

            // errors on input
            for (int i = 0; i <= icount; i++)
            {
                ie[i] = 0;
                for (int h = 0; h < hcount; h++)
                {
                    ie[i] += he[h] * w1[i, h];
                }
            }

            for(int h = 0; h < hcount; h++)
            {
                w1[0,h] += rate*he[h]*dsig(hidsums[h]);
                for (int i = 0; i < icount; i++)
                {
                    w1[i+1, h] += rate * he[h] * dsig(hidsums[h]) * ins[i];
                }
            }

            for(int o = 0; o < ocount; o++)
            {
                w2[0,o] += rate * oe[o] * dsig(outsums[o]);
                for(int h = 0; h < hcount; h++)
                {
                    w2[h+1,o] += rate * oe[o] * dsig(outsums[o]) * hid[h];
                }
            }

        }

        public double train(double[] inputs, double[] target)
        {
            this.ins = inputs;
            input(inputs);

            backprop(target);

            double error = 0;
            for (int i = 0; i < ocount; i++)
            {
                error += Math.Pow(outs[i] - target[i], 2);
            }
            return 0.5 * error;
        }

        // sigmoid
        private double sig(double x)
        {
            return 1 / (1 + Math.Exp(-x));
        }

        // derivative of sigmoid
        private double dsig(double x)
        {
            double s = sig(x);
            return s * (1 - s);
        }

        public void printW1()
        {
            Trace.WriteLine(w1.GetLength(0));
            Trace.WriteLine(w1.GetLength(1));
            foreach (double item in w1)
            {
                Trace.Write(item + " ");
            }
            Trace.WriteLine("");
        }

        public void printW2()
        {
            Trace.WriteLine(w2.GetLength(0));
            Trace.WriteLine(w2.GetLength(1));
            foreach (double item in w2)
            {
                Trace.Write(item + " ");
            }
            Trace.WriteLine("");
        }
    }
}
